/******************************************************************************
 * Copyright (C) 2016  671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#include "node/system/event_POSIX.h"
#include <assert.h>
#include <stdexcept>
#include <memory>
#include <sys/time.h>

namespace node {
	namespace system {

	event_impl::event_impl(bool init_state, bool auto_reset)
		: _event(event_impl::create_event(init_state, auto_reset))
	{
		if (_event == 0) throw std::runtime_error("failed create event");
	}
	event_impl::~event_impl(void)
	{
		if (!_event)
		{
			destroy_event(_event);
			_event = 0;
		}
	}

	struct event_impl::sys_event* create_event(bool init_state, bool auto_reset)
	{
		std::auto_ptr<struct event_impl::sys_event> event(new(std::nothrow) struct event_impl::sys_event);
		if (event.get() == 0) {
			return 0;
		}
		event->state = init_state;
		event->manual_reset = !auto_reset;
		if (pthread_mutex_init(&(event->mutex), 0)) {
			return 0;
		}
		if (pthread_cond_init(&(event->cond), 0)) {
			return 0;
		}
		return event.release();
	}
	void destroy_event(struct event_impl::sys_event* event)
	{
		std::auto_ptr<struct event_impl::sys_event> del_event(event);
		pthread_cond_destroy(&(del_event->cond));
		pthread_mutex_destroy(&(del_event->mutex));
	}
	bool set_event(struct event_impl::sys_event* event)
	{
		assert(event);
		if (pthread_mutex_lock(&(event->mutex)) != 0) {
			return -1;
		}
		{
			event->state = true;
			if (event->manual_reset) {
				if (pthread_cond_broadcast(&(event->cond))) {
					return -1;
				}
			} else {
				if (pthread_cond_signal(&(event->cond))) {
					return -1;
				}
			}
		}
		if (pthread_mutex_unlock(&(event->mutex)) != 0) {
			return -1;
		}
		return 0;
	}
	bool reset_event(struct event_impl::sys_event* event)
	{
		assert(event);
		if (pthread_mutex_lock(&(event->mutex)) != 0) {
			return -1;
		}
		{
			event->state = false;
		}

		if (pthread_mutex_unlock(&(event->mutex)) != 0) {
			return -1;
		}
		return 0;
	}
	bool wait_event(struct event_impl::sys_event* event)
	{
		assert(event);
		if (pthread_mutex_lock(&(event->mutex)) != 0) {
			return -1;
		}
		{
			while (!event->state) {
				if (pthread_cond_wait(&(event->cond), &(event->mutex))) {
					pthread_mutex_unlock(&(event->mutex));
					return -1;
				}
			}

			if (!event->manual_reset) {
				event->state = false;
			}
		}
		if (pthread_mutex_unlock(&(event->mutex)) != 0) {
			return -1;
		}
		return 0;
	}
	bool wait_event(struct event_impl::sys_event* event, long milliseconds)
	{
		assert(event);
		int rc = 0;
		struct timespec abstime;
		struct timeval tv;
		gettimeofday(&tv, NULL);
		abstime.tv_sec = tv.tv_sec + milliseconds / 1000;
		abstime.tv_nsec = tv.tv_usec * 1000 + (milliseconds % 1000) * 1000000;
		if (abstime.tv_nsec >= 1000000000) {
			abstime.tv_nsec -= 1000000000;
			abstime.tv_sec += 1;
		}

		if (pthread_mutex_lock(&(event->mutex)) != 0) {
			return -1;
		}
		{
			while (!event->state) {
				if ((rc = pthread_cond_timedwait(&(event->cond), &(event->mutex), &abstime))) {
					if (rc == ETIMEDOUT) break;
					pthread_mutex_unlock(&(event->mutex));
					return -1;
				}
			}
			if (rc == 0 && !event->manual_reset) {
				event->state = false;
			}
		}
		if (pthread_mutex_unlock(&(event->mutex)) != 0) {
			return -1;
		}
		return 0;
	}

}}